home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / Palettes / Chart / Source / BarChart.m < prev    next >
Text File  |  1995-06-12  |  35KB  |  1,417 lines

  1. // -------------------------------------------------------------------------------------
  2. // BarChart.m
  3. // Martin D. Flynn, NeXT Computer, Inc.
  4. // -------------------------------------------------------------------------------------
  5.  
  6. #import <appkit/appkit.h>
  7. #import <libc.h>
  8. #import <c.h>
  9. #import <stdlib.h>
  10. #import <string.h>
  11. #import <math.h>
  12. #import <dpsclient/wraps.h>
  13. #import <streams/streams.h>
  14. #import <objc/Storage.h>
  15. #import <objc/List.h>
  16. #import "BarChartPS.h"
  17. #import "BarChart.h"
  18.  
  19. // -------------------------------------------------------------------------------------
  20. // archive version number
  21. #define    VERSION            2
  22.  
  23. // -------------------------------------------------------------------------------------
  24. // data source types
  25. #define    srcTypeSAMPLE    -1
  26. #define    srcTypeNONE        0
  27. #define    srcTypeTEXT        1
  28. #define    srcTypeMATRIX    2
  29. #define    srcTypeSTORAGE    3
  30. #define    srcTypeSTREAM    4
  31.  
  32. // -------------------------------------------------------------------------------------
  33. #define    X                origin.x
  34. #define    Y                origin.y
  35. #define    W                size.width
  36. #define    H                size.height
  37. #define    fontWIDTH(L)    [currentFont getWidthOf:L]
  38.  
  39. // -------------------------------------------------------------------------------------
  40. // x/y plot/scale conversions
  41. #define    xPOINT(P)        (cFlags.xLogScale? [self log:(P)] : (P))
  42. #define    yPOINT(P)        (cFlags.yLogScale? [self log:(P)] : (P))
  43. #define    xSCALE(P)        (dataScale.x * xPOINT(P))
  44. #define    ySCALE(P)        (dataScale.y * yPOINT(P))
  45. #define    xPLOT(P)        (axisOrigin.x + xSCALE(P))
  46. #define    yPLOT(P)        (axisOrigin.y + ySCALE(P))
  47.  
  48. // -------------------------------------------------------------------------------------
  49. @implementation BarChart
  50.  
  51. // -------------------------------------------------------------------------------------
  52. // set archive version number
  53. static BOOL    _nibMode = NO;
  54. + initialize
  55. {
  56.   [self setVersion:VERSION];
  57.   if (!strcmp([NXApp appName], "InterfaceBuilder")) _nibMode = YES;
  58.   return self;
  59. }
  60.  
  61. // -------------------------------------------------------------------------------------
  62. // return inspector name
  63. - (const char*)getInspectorClassName { return "BarChartInspector"; }
  64.  
  65. // -------------------------------------------------------------------------------------
  66. // color support 
  67.  
  68. /* convert color to gray */
  69. static float _colorGray(NXColor color)
  70. {
  71.   float    gray;
  72.   NXConvertColorToGray(color, &gray);
  73.   return gray;
  74. }
  75.  
  76. /* set color/gray */
  77. static void _setColor(NXColor color, BOOL isColor)
  78. {
  79.   if (isColor) NXSetColor(color); else PSsetgray(_colorGray(color));
  80. }
  81.  
  82. // -------------------------------------------------------------------------------------
  83. // new
  84. - initFrame:(const NXRect*)r
  85. {
  86.  
  87.   /* instantiate view */
  88.   self = [super initFrame:r];
  89.   isFirstResponder = NO;
  90.  
  91.   /* init font */
  92.   currentFont = [Font newFont:"Helvetica" size:10.0 matrix:NX_IDENTITYMATRIX];  
  93.  
  94.   /* initialize flags */
  95.   memset(&cFlags, 0, sizeof(cFlags));
  96.   cFlags.xShowLabels = YES;
  97.   cFlags.xLogScale     = NO;
  98.   cFlags.xGridDraw     = NO;
  99.   cFlags.yShowLabels = YES;
  100.   cFlags.yLogScale     = NO;
  101.   cFlags.yGridDraw     = YES;
  102.   cFlags.drawInColor = NO;
  103.  
  104.   /* initialize colors */
  105.   backgroundColor = NXConvertGrayToColor(NX_LTGRAY);
  106.   axisColor          = NXConvertGrayToColor(NX_BLACK);
  107.   yGridColor      = NXConvertGrayToColor(NX_WHITE);
  108.   xGridColor      = NXConvertGrayToColor(NX_WHITE);
  109.   
  110.   /* default chart (for IB use) */
  111.   dftChartType  = chartBAR;
  112.   dftChartColor = NXConvertGrayToColor(NX_DKGRAY);
  113.  
  114.   /* init outlets */
  115.   delegate         = self;
  116.   xLabelMatrix     = (id)nil;
  117.   yLabelMatrix     = (id)nil;
  118.   actionMatrix     = (id)nil;
  119.  
  120.   /* init defaut chart */
  121.   dataList      = (id)nil;
  122.   dataRange.X = 0.0;
  123.   dataRange.Y = 0.0;
  124.   dataRange.W = 10.0;
  125.   dataRange.H = 5.0;
  126.   tickMark.X  = 0.0;
  127.   tickMark.Y  = 0.0;
  128.   tickMark.W  = 2.0;
  129.   tickMark.H  = 1.0;
  130.  
  131.   /* flag for rescaling */
  132.   reScale = YES;
  133.   
  134.   return self;
  135. }
  136.  
  137. /* free */
  138. - free
  139. {
  140.   if (dataList) [dataList free];
  141.   return [super free];
  142. }
  143.  
  144. // -------------------------------------------------------------------------------------
  145. // access to internal charting data structure list
  146. // -------------------------------------------------------------------------------------
  147.  
  148. /* return charting data list */
  149. - (Storage*)chartList
  150. {
  151.   return dataList;
  152. }
  153.  
  154. /* return number of chart types in list */
  155. - (int)chartCount
  156. {
  157.   return [dataList count];
  158. }
  159.  
  160. /* return pointer to specific chart item */
  161. - (chartData_s*)chartDataAt:(int)index
  162. {
  163.   return (chartData_s*)[dataList elementAt:index];
  164. }
  165.  
  166. // -------------------------------------------------------------------------------------
  167. // log conversion method
  168. // -------------------------------------------------------------------------------------
  169.  
  170. /* return log converted data point */
  171. - (float)log:(float)num
  172. {
  173.   return num? (float)log10(fabs((double)num)) : 0.0;
  174. }
  175.  
  176. /* return antiLog converted data point */
  177. - (float)exp:(float)num
  178. {
  179.   return (float)pow((double)10.0, (double)num);
  180. }
  181.  
  182. // -------------------------------------------------------------------------------------
  183. // chart data point conversion
  184. // -------------------------------------------------------------------------------------
  185.  
  186. /* convert data point to chart */
  187. - convertDataPointToChart:(NXPoint*)point
  188. {
  189.   point->x = xPLOT(point->x);
  190.   point->y = xPLOT(point->y);
  191.   return self;
  192. }
  193.  
  194. /* convert chart point to data */
  195. - convertChartPointToData:(NXPoint*)point
  196. {
  197.   NXPoint    p;
  198.   p.x = (point->x - axisOrigin.x) / dataScale.x;
  199.   p.y = (point->y - axisOrigin.y) / dataScale.y;
  200.   point->x = cFlags.xLogScale? [self exp:p.x] : p.x;
  201.   point->y = cFlags.yLogScale? [self exp:p.y] : p.y;
  202.   return self;
  203. }
  204.  
  205. // -------------------------------------------------------------------------------------
  206. // set plot options
  207. // -------------------------------------------------------------------------------------
  208.  
  209. /* set delegate */
  210. - setDelegate:anObject
  211. {
  212.   delegate = anObject;
  213.   return self;
  214. }
  215.  
  216. /* return current delegate */
  217. - delegate
  218. {
  219.   return delegate;
  220. }
  221.  
  222. /* set font (make a new unflipped font copy) */
  223. // This is done this way on purpose
  224. - setFont:fontId
  225. {
  226.   currentFont = [Font newFont:[fontId name] size:[fontId pointSize] matrix:NX_IDENTITYMATRIX];  
  227.   reScale = YES;
  228.   return self;
  229. }
  230.  
  231. /* return current font (NX_FLIPPEDMATRIX) */
  232. // This is done this way on purpose (IB can't handle it properly any other way)
  233. - font
  234. {
  235.   return [Font newFont:[currentFont name] size:[currentFont pointSize]];
  236. }
  237.  
  238. /* return isColor status */
  239. - (BOOL)drawInColor
  240. {
  241.   return cFlags.drawInColor;
  242. }
  243.  
  244. /* set background transparent */
  245. - setBackgroundTransparent:(BOOL)flag
  246. {
  247.   cFlags.transparent = flag;
  248.   return self;
  249. }
  250.  
  251. /* return background transparent state */
  252. - (BOOL)backgroundTransparent
  253. {
  254.   return cFlags.transparent;
  255. }
  256.  
  257. /* set background gray */
  258. - setBackgroundGray:(float)gray
  259. {
  260.   backgroundColor = NXConvertGrayToColor(gray);
  261.   cFlags.drawInColor = NO;
  262.   return self;
  263. }
  264.  
  265. /* return background gray */
  266. - (float)backgroundGray
  267. {
  268.   return _colorGray(backgroundColor);
  269. }
  270.  
  271. /* set background color */
  272. - setBackgroundColor:(NXColor)color
  273. {
  274.   backgroundColor = color;
  275.   cFlags.drawInColor = YES;
  276.   return self;
  277. }
  278.  
  279. /* return background color */
  280. - (NXColor)backgroundColor
  281. {
  282.   return backgroundColor;
  283. }
  284.  
  285. /* set default chart gray */
  286. - setDftChartGray:(float)gray
  287. {
  288.   dftChartColor = NXConvertGrayToColor(gray);
  289.   cFlags.drawInColor = NO;
  290.   return self;
  291. }
  292.  
  293. /* return default chart gray */
  294. - (float)dftChartGray
  295. {
  296.   return _colorGray(dftChartColor);
  297. }
  298.  
  299. /* set default chart gray */
  300. - setDftChartColor:(NXColor)color
  301. {
  302.   dftChartColor = color;
  303.   cFlags.drawInColor = YES;
  304.   return self;
  305. }
  306.  
  307. /* return default chart color */
  308. - (NXColor)dftChartColor
  309. {
  310.   return dftChartColor;
  311. }
  312.  
  313. /* set axis gray */
  314. - setAxisGray:(float)gray
  315. {
  316.   axisColor = NXConvertGrayToColor(gray);
  317.   cFlags.drawInColor = NO;
  318.   return self;
  319. }
  320.  
  321. /* return axis gray */
  322. - (float)axisGray
  323. {
  324.   return _colorGray(axisColor);
  325. }
  326.  
  327. /* set axis color */
  328. - setAxisColor:(NXColor)color
  329. {
  330.   axisColor = color;
  331.   cFlags.drawInColor = YES;
  332.   return self;
  333. }
  334.  
  335. /* return axis color */
  336. - (NXColor)axisColor
  337. {
  338.   return axisColor;
  339. }
  340.  
  341. /* set to draw x grid */
  342. - setXGridDraw:(BOOL)flag
  343. {
  344.   cFlags.xGridDraw = flag;
  345.   return self;
  346. }
  347.  
  348. /* return x grid drawing status */
  349. - (BOOL)xGridDraw
  350. {
  351.   return cFlags.xGridDraw;
  352. }
  353.  
  354. /* set x grid gray */
  355. - setXGridGray:(float)gray
  356. {
  357.   xGridColor = NXConvertGrayToColor(gray);
  358.   cFlags.drawInColor = NO;
  359.   return self;
  360. }
  361.  
  362. /* return x grid gray */
  363. - (float)xGridGray
  364. {
  365.   return _colorGray(xGridColor);
  366. }
  367.  
  368. /* set x grid color */
  369. - setXGridColor:(NXColor)color
  370. {
  371.   xGridColor = color;
  372.   cFlags.drawInColor = YES;
  373.   return self;
  374. }
  375.  
  376. /* return x grid color */
  377. - (NXColor)xGridColor
  378. {
  379.   return xGridColor;
  380. }
  381.  
  382. /* set to draw y grid */
  383. - setYGridDraw:(BOOL)flag
  384. {
  385.   cFlags.yGridDraw = flag;
  386.   return self;
  387. }
  388.  
  389. /* return y grid drawing status */
  390. - (BOOL)yGridDraw
  391. {
  392.   return cFlags.yGridDraw;
  393. }
  394.  
  395. /* set y grid gray */
  396. - setYGridGray:(float)gray
  397. {
  398.   yGridColor = NXConvertGrayToColor(gray);
  399.   cFlags.drawInColor = NO;
  400.   return self;
  401. }
  402.  
  403. /* return y grid gray */
  404. - (float)yGridGray
  405. {
  406.   return _colorGray(yGridColor);
  407. }
  408.  
  409. /* set y grid color */
  410. - setYGridColor:(NXColor)color
  411. {
  412.   yGridColor = color;
  413.   cFlags.drawInColor = YES;
  414.   return self;
  415. }
  416.  
  417. /* return y grid color */
  418. - (NXColor)yGridColor
  419. {
  420.   return yGridColor;
  421. }
  422.  
  423. /* set default chart type */
  424. - setDftChartType:(int)chartType
  425. {
  426.   dftChartType = chartType;
  427.   return self;
  428. }
  429.  
  430. /* return default chart type */
  431. - (int)dftChartType
  432. {
  433.   return dftChartType;
  434. }
  435.  
  436. /* data range */
  437. - setDataRange:(NXRect*)range
  438. {
  439.   dataRange = *range;
  440.   reScale = YES;
  441.   return self;
  442. }
  443.  
  444. /* return data range */
  445. - (NXRect*)dataRange
  446. {
  447.   return &dataRange;
  448. }
  449.  
  450. /* tick marks (origin currently not used) */
  451. - setTickMark:(NXRect*)tick
  452. {
  453.   tickMark = *tick;
  454.   tickMark.X = tickMark.Y = 0.0;
  455.   reScale = YES;
  456.   return self;
  457. }
  458.  
  459. /* return tick mark settings */
  460. - (NXRect*)tickMark
  461. {
  462.   return &tickMark;
  463. }
  464.  
  465. /* set x log scaling */
  466. - setXLogScaling:(BOOL)flag
  467. {
  468.   cFlags.xLogScale = flag;
  469.   reScale = YES;
  470.   return self;
  471. }
  472.  
  473. /* return x log scaling */
  474. - (BOOL)xLogScaling
  475. {
  476.   return cFlags.xLogScale;
  477. }
  478.  
  479. /* set y log scaling */
  480. - setYLogScaling:(BOOL)flag
  481. {
  482.   cFlags.yLogScale = flag;
  483.   reScale = YES;
  484.   return self;
  485. }
  486.  
  487. /* return y log scaling */
  488. - (BOOL)yLogScaling
  489. {
  490.   return cFlags.yLogScale;
  491. }
  492.  
  493. /* set x show labels */
  494. - setXShowLabels:(BOOL)flag
  495. {
  496.   cFlags.xShowLabels = flag;
  497.   reScale = YES;
  498.   return self;
  499. }
  500.  
  501. /* return x show labels */
  502. - (BOOL)xShowLabels
  503. {
  504.   return cFlags.xShowLabels;
  505. }
  506.  
  507. /* set y show labels */
  508. - setYShowLabels:(BOOL)flag
  509. {
  510.   cFlags.yShowLabels = flag;
  511.   reScale = YES;
  512.   return self;
  513. }
  514.  
  515. /* return y show labels */
  516. - (BOOL)yShowLabels
  517. {
  518.   return cFlags.yShowLabels;
  519. }
  520.  
  521. /* rotate x axis labels */
  522. - setXLabelRotate:(BOOL)flag
  523. {
  524.   cFlags.xLabelRotate = flag;
  525.   reScale = YES;
  526.   return self;
  527. }
  528.  
  529. /* return X axis label rotate state */
  530. - (BOOL)xLabelRotate
  531. {
  532.   return cFlags.xLabelRotate;
  533. }
  534.  
  535. /* rotate y axis labels */
  536. - setYLabelRotate:(BOOL)flag
  537. {
  538.   cFlags.yLabelRotate = flag;
  539.   reScale = YES;
  540.   return self;
  541. }
  542.  
  543. /* return Y axis label rotate state */
  544. - (BOOL)yLabelRotate
  545. {
  546.   return cFlags.yLabelRotate;
  547. }
  548.  
  549. // -------------------------------------------------------------------------------------
  550. // actions
  551. // -------------------------------------------------------------------------------------
  552.  
  553. /* redisplay chart from source */
  554. - update:sender
  555. {
  556.   return [self display];
  557. }
  558.  
  559. /* override from super to rescale if frame changes */
  560. - sizeTo:(NXCoord)w :(NXCoord)h
  561. {
  562.   reScale = YES;
  563.   return [super sizeTo:w :h];
  564. }
  565.  
  566. /* add data source */
  567. - (int)_addData:srcId:(int)sType chart:(int)cType:(float)width:(NXColor)color:(BOOL)isColor
  568. {
  569.   int            count;
  570.   chartData_s    src = { sType, srcId, cType, NO, width, isColor, color };
  571.   if (sType == srcTypeNONE) return -1;
  572.   if (!dataList) dataList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
  573.       description:(char*)nil] empty];
  574.   count = [dataList count];
  575.   [dataList addElement:&src];
  576.   return count;
  577. }
  578.  
  579. /* return source type */
  580. - (int)_srcType:sourceId
  581. {
  582.   if ([sourceId isKindOf:[Text    class]]) return srcTypeTEXT;
  583.   if ([sourceId isKindOf:[Matrix  class]]) return srcTypeMATRIX;
  584.   if ([sourceId isKindOf:[Storage class]]) return srcTypeSTORAGE;
  585.   return srcTypeNONE;
  586. }
  587.  
  588. /* add data source (returns list index) (gray) */
  589. - (int)addDataSource:sourceId type:(int)type lineWidth:(float)width gray:(float)gray
  590. {
  591.   NXColor    color = NXConvertGrayToColor(gray);
  592.   id        src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
  593.   return [self _addData:src:[self _srcType:src] chart:type:width:color:NO];
  594. }
  595.  
  596. /* add data source (returns list index) (color) */
  597. - (int)addDataSource:sourceId type:(int)type lineWidth:(float)width color:(NXColor)color
  598. {
  599.   id    src = ([sourceId isKindOf:[ScrollView class]])? [sourceId docView] : sourceId;
  600.   return [self _addData:src:[self _srcType:src] chart:type:width:color:YES];
  601. }
  602.  
  603. /* add data stream (gray) */
  604. - (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width gray:(float)gray
  605. {
  606.   NXColor    color = NXConvertGrayToColor(gray);
  607.   return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:NO];
  608. }
  609.  
  610. /* add data stream (color) */
  611. - (int)addDataStream:(NXStream*)s type:(int)type lineWidth:(float)width color:(NXColor)color
  612. {
  613.   return [self _addData:(id)s:srcTypeSTREAM chart:type:width:color:YES];
  614. }
  615.  
  616. /* default data source outlet (if added, index will always be zero) */
  617. - setDataSource:anObject
  618. {
  619.   int        n;
  620.   NXColor    color = dftChartColor;
  621.   if (dataList) { [dataList free]; dataList = (id)nil; }
  622.   if (!anObject) return self;
  623.   n = cFlags.drawInColor?
  624.       [self addDataSource:anObject type:dftChartType lineWidth:-1.0 color:color]:
  625.     [self addDataSource:anObject type:dftChartType lineWidth:-1.0 gray:_colorGray(color)];
  626.   if (n >= 0) [self chartDataAt:n]->isEditable = YES;
  627.   return self;
  628. }
  629.  
  630. /* set x label matrix */
  631. - setXLabelMatrix:anObject
  632. {
  633.   if (![anObject isKindOf:[Matrix class]]) return (id)nil;
  634.   xLabelMatrix = anObject;
  635.   reScale = YES;
  636.   return self;
  637. }
  638.  
  639. /* return current x-axis label matrix */
  640. - xLabelMatrix
  641. {
  642.   return xLabelMatrix;
  643. }
  644.  
  645. /* set y label matrix */
  646. - setYLabelMatrix:anObject
  647. {
  648.   if (![anObject isKindOf:[Matrix class]]) return (id)nil;
  649.   yLabelMatrix = anObject;
  650.   reScale = YES;
  651.   return self;
  652. }
  653.  
  654. /* return current y-axis label matrix */
  655. - yLabelMatrix
  656. {
  657.   return yLabelMatrix;
  658. }
  659.  
  660. // -------------------------------------------------------------------------------------
  661. // data retrieval methods
  662. // -------------------------------------------------------------------------------------
  663.  
  664. /* return number of points in data chart set */
  665. - (int)_pointCount:(chartData_s*)dtaPtr
  666. {
  667.   if (!dtaPtr) return MIN((floor(dataRange.W) - 1.0), 30.0); // inadequate for xLogScale?
  668.   switch (dtaPtr->_srcType) {
  669.     case srcTypeMATRIX:        return [[dtaPtr->_srcId cellList] count];
  670.     case srcTypeSTORAGE:    return [dtaPtr->_srcId count];
  671.   }
  672.   return 0;
  673. }
  674.  
  675. /* return specific point */
  676. #define RND(S)    (srandom(S*21),(float)(random()&0xFFFF)/(float)0xFFFF)
  677. - _dataItemAt:(int)index :(chartData_s*)dtaPtr :(float*)x:(float*)y
  678. {
  679.   id        cellId;
  680.   NXPoint    *pt;
  681.   
  682.   /* check for sample default chart type */
  683.   if (!dtaPtr) {
  684.     float gx, gy;
  685.     if (!_nibMode) return (id)nil;
  686.     gx = dataRange.X+1.0+(float)(index*(cFlags.xLogScale?rint(dataRange.X+10.0/10.0):1));
  687.     gy = dataRange.Y+dataRange.H*(cFlags.yLogScale?0.15:0.3)*(1.0+RND(index));
  688.     *x = xPLOT(gx);
  689.     *y = yPLOT(gy);
  690.     return self;
  691.   }
  692.   
  693.   /* normal chart type */
  694.   switch (dtaPtr->_srcType) {
  695.     case srcTypeMATRIX:        // Matrix
  696.       if (!(cellId = [[dtaPtr->_srcId cellList] objectAt:index])) return (id)nil;
  697.       if (![cellId respondsTo:@selector(floatValue)]) return (id)nil;
  698.       *y = yPLOT([cellId floatValue]);
  699.       *x = xPLOT((float)[cellId tag]);
  700.       return self;
  701.     case srcTypeSTORAGE:    // Storage
  702.       pt = (NXPoint*)[dtaPtr->_srcId elementAt:index];
  703.       *x = xPLOT(pt->x);
  704.       *y = yPLOT(pt->y);
  705.       return self;
  706.   }
  707.   
  708.   return (id)nil;
  709. }
  710.  
  711. /* build storage object containing points fount in stream (MUST BE EXPLICITLY FREED) */
  712. - _parseStream:(NXStream*)s :(BOOL)all
  713. {
  714.   int            chartType = dftChartType;
  715.   NXColor        chartColor;
  716.   BOOL            chartIsColor = NO, newChart;
  717.   chartData_s    dtaSrc;
  718.   id            dList = (id)nil;
  719.  
  720.   /* reset to beginning */
  721.   NXSeek(s, 0L, NX_FROMSTART);
  722.   
  723.   /* read stream */
  724.   for (newChart = YES; !NXAtEOS(s); ) {
  725.     int v;
  726.     NXPoint pt;
  727.     char *p, b[256];
  728.   
  729.     /* read line */
  730.     for (p = b; !NXAtEOS(s); p++) {
  731.       *p = (char)NXGetc(s);
  732.       if (!*p || (*p == '\n')) break;
  733.     } *p = 0;
  734.     
  735.     /* check for command */
  736.     if ((*b == ':') && !strncasecmp(b, ":chart ", 7)) {
  737.       char typeStr[256];
  738.       float red = -1.0, green = -1.0, blue = -1.0;
  739.       int n, rtn;
  740.       
  741.       /* default chart */
  742.       newChart = YES;
  743.       chartType = dftChartType;
  744.       chartColor = dftChartColor;
  745.       chartIsColor = cFlags.drawInColor;
  746.       if (sscanf(b, "%*s %s %n", typeStr, &n) != 1) continue;
  747.       
  748.       /* change chart type */
  749.       chartType = 0;
  750.       if (index(typeStr, '*')) chartType  = dftChartType;
  751.       if (index(typeStr, 'b')) chartType |= chartBAR;
  752.       if (index(typeStr, 'l')) chartType |= chartLINE;
  753.       if (index(typeStr, 'a')) chartType |= chartAREA;
  754.       if (index(typeStr, 'p')) chartType |= chartPOINT;
  755.       if (n > strlen(b)) continue;
  756.       
  757.       /* change color by name */
  758.       if (b[n] == '*') {
  759.         char    c[128];
  760.         sscanf(&b[n+1], "%s", c);
  761.         if (!strcasecmp(c,"black"  )) {chartColor=NX_COLORBLACK  ; chartIsColor=NO ;} else
  762.         if (!strcasecmp(c,"dkgray" )) {chartColor=NX_COLORDKGRAY ; chartIsColor=NO ;} else
  763.         if (!strcasecmp(c,"ltgray" )) {chartColor=NX_COLORLTGRAY ; chartIsColor=NO ;} else
  764.         if (!strcasecmp(c,"white"  )) {chartColor=NX_COLORWHITE  ; chartIsColor=NO ;} else
  765.         if (!strcasecmp(c,"gray"   )) {chartColor=NX_COLORGRAY   ; chartIsColor=YES;} else
  766.         if (!strcasecmp(c,"red"    )) {chartColor=NX_COLORRED    ; chartIsColor=YES;} else
  767.         if (!strcasecmp(c,"orange" )) {chartColor=NX_COLORORANGE ; chartIsColor=YES;} else
  768.         if (!strcasecmp(c,"yellow" )) {chartColor=NX_COLORYELLOW ; chartIsColor=YES;} else
  769.         if (!strcasecmp(c,"green"  )) {chartColor=NX_COLORGREEN  ; chartIsColor=YES;} else
  770.         if (!strcasecmp(c,"blue"   )) {chartColor=NX_COLORBLUE   ; chartIsColor=YES;} else
  771.         if (!strcasecmp(c,"purple" )) {chartColor=NX_COLORPURPLE ; chartIsColor=YES;} else
  772.         if (!strcasecmp(c,"cyan"   )) {chartColor=NX_COLORCYAN   ; chartIsColor=YES;} else
  773.         if (!strcasecmp(c,"brown"  )) {chartColor=NX_COLORBROWN  ; chartIsColor=YES;} else
  774.         if (!strcasecmp(c,"magenta")) {chartColor=NX_COLORMAGENTA; chartIsColor=YES;} else
  775.         if (!strcasecmp(c,"clear"  )) {chartColor=NX_COLORCLEAR  ; chartIsColor=YES;}
  776.         continue;
  777.       }
  778.       
  779.       /* change color by number */
  780.       rtn = sscanf(&b[n], "%f %f %f", &red, &green, &blue);
  781.       chartIsColor = (rtn == 3)? YES : NO;
  782.       if (chartIsColor) chartColor = NXConvertRGBToColor(red, green, blue);
  783.       else chartColor = NXConvertGrayToColor(red);
  784.       continue;
  785.     }
  786.  
  787.     /* new chart */
  788.     if (newChart) {
  789.       if (dList && !all) break;    // already have the first set
  790.       memset(&dtaSrc, 0, sizeof(chartData_s));
  791.       dtaSrc._srcType = srcTypeSTORAGE;
  792.       dtaSrc._srcId   = [[[Storage alloc] initCount:1 elementSize:sizeof(NXPoint)
  793.           description:(char*)nil] empty];
  794.       dtaSrc.chartType  = chartType;
  795.       dtaSrc.isEditable = NO;
  796.       dtaSrc.lineWidth  = -1.0;
  797.       dtaSrc.isColor    = chartIsColor;
  798.       dtaSrc.chartColor = chartColor;
  799.       if (!dList) dList = [[[Storage alloc] initCount:1 elementSize:sizeof(chartData_s)
  800.             description:(char*)nil] empty];
  801.       [dList addElement:&dtaSrc];
  802.       newChart = NO;
  803.     }
  804.     
  805.     /* parse data point */
  806.     v = sscanf(b, "%f %f ", &pt.x, &pt.y);
  807.     if (v <= 0) continue;
  808.     if (v == 1) { pt.y = pt.x; pt.x = (float)[dtaSrc._srcId count]; }
  809.     [dtaSrc._srcId addElement:&pt];
  810.     
  811.   }
  812.   
  813.   /* return parse data list */
  814.   if (dList && ![dList count]) { [dList free]; dList = (id)nil; }
  815.   return dList;
  816.   
  817. }
  818.  
  819. // -------------------------------------------------------------------------------------
  820. // plot chart
  821. // -------------------------------------------------------------------------------------
  822.  
  823. /* chart data */
  824. - _chartData:(chartData_s*)dtaPtr
  825. {
  826.   BOOL    first;
  827.   float    x, y, lineWidth;
  828.   int    i, cnt = [self _pointCount:dtaPtr], chartType;
  829.  
  830.   /* check for sample chart type */
  831.   if (dtaPtr) {
  832.     if (dtaPtr->isColor) NXSetColor(dtaPtr->chartColor);
  833.     else PSsetgray(_colorGray(dtaPtr->chartColor));
  834.     chartType = dtaPtr->chartType;
  835.     lineWidth = dtaPtr->lineWidth;
  836.   } else {
  837.     _setColor(dftChartColor, cFlags.drawInColor);
  838.     chartType = dftChartType;
  839.     lineWidth = -1.0;
  840.   }
  841.  
  842.   /* area chart */
  843.   if (chartType & chartAREA) {
  844.     PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
  845.     for (first = YES, i = 0; i < cnt ; i++) {
  846.       if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
  847.       if (first) { PSmoveto(x, axisOrigin.y); first = NO; }
  848.       PSlineto(x, y);
  849.     }
  850.     if (!first) { PSlineto(x, axisOrigin.y); PSclosepath(); PSfill(); }
  851.   }
  852.  
  853.   /* bar chart */
  854.   if (chartType & chartBAR) {
  855.     PSsetlinewidth((lineWidth>=0.0)?xSCALE(lineWidth):xSCALE(0.8));
  856.     for (i = 0; i < cnt ; i++) {
  857.       if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
  858.       _barALine(x, axisOrigin.y, x, y);
  859.     }
  860.   }
  861.  
  862.   /* line chart */
  863.   if (chartType & chartLINE) {
  864.     PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
  865.     PSnewpath();
  866.     for (first = YES, i = 0; i < cnt ; i++) {
  867.       if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
  868.       if (first) { PSmoveto(x, y); first = NO; }
  869.       else PSlineto(x, y);
  870.     }
  871.     if (!first) PSstroke();
  872.   }
  873.  
  874.   /* point chart */
  875.   if (chartType & chartPOINT) {
  876.     PSsetlinewidth((lineWidth>=0.0)?lineWidth:1.0);
  877.     for (i = 0; i < cnt ; i++) {
  878.       if (![self _dataItemAt:i :dtaPtr :&x:&y]) break;
  879.       _barDrawPoint(x, y, 3.0);
  880.     }
  881.   }
  882.   
  883.   return self;
  884. }
  885.  
  886. // -------------------------------------------------------------------------------------
  887. // draw chart view
  888. // -------------------------------------------------------------------------------------
  889.  
  890. /* calculate printed decimal precision */
  891. static int _dPrecision(float num)
  892. {
  893.   int        p;
  894.   char        floatStr[32], *fPtr;
  895.   if (num > 9999.0) return 0;
  896.   sprintf(floatStr, "%f", num);
  897.   for (fPtr = floatStr+strlen(floatStr)-1; (*fPtr=='0') || (*fPtr==' ');) *(fPtr--) = 0;
  898.   for (fPtr = floatStr; *fPtr && (*fPtr != '.'); fPtr++);
  899.   p = (*fPtr)? strlen(fPtr + 1) : 0;
  900.   return p;
  901. }
  902.  
  903. /* rescale axis */
  904. - _rescaleAxis
  905. {
  906.   char        label[64];
  907.   NXSize    xLS, yLS;
  908.   float        fontHeight = [currentFont pointSize] * 0.76;
  909.  
  910.   /* save upper range limit */
  911.   maxRange.x = dataRange.X + dataRange.W;
  912.   maxRange.y = dataRange.Y + dataRange.H;
  913.  
  914.   /* check number of tick marks */
  915.   if (!cFlags.xLogScale && (dataRange.W / tickMark.W >= 250.0))
  916.       tickMark.W = dataRange.W / 250.0;
  917.   if (!cFlags.yLogScale && (dataRange.H / tickMark.H >= 250.0))
  918.       tickMark.H = dataRange.H / 250.0;
  919.  
  920.   /* calc tick mark length and line width */
  921.   tickLength = MAX(MIN(fontHeight, 12.0), 5.0);
  922.   tickWidth  = ([currentFont pointSize] >= 20.0)? 2.0 : 1.0;
  923.  
  924.   /* label precision */
  925.   precisionX = _dPrecision(tickMark.W);
  926.   precisionY = _dPrecision(tickMark.H);
  927.     
  928.   /* label size */
  929.   if (xLabelMatrix) [xLabelMatrix getCellSize:&xLblSize];
  930.   else {
  931.     sprintf(label, "%.*f", precisionX, maxRange.x);
  932.     xLblSize.width  = fontWIDTH(label);
  933.     xLblSize.height = fontHeight;
  934.   }
  935.   if (!cFlags.xLabelRotate) xLS = xLblSize;
  936.   else { xLS.width = xLblSize.height; xLS.height = xLblSize.width; }
  937.   if (yLabelMatrix) [yLabelMatrix getCellSize:&yLblSize];
  938.   else {
  939.     sprintf(label, "%.*f", precisionY, maxRange.y);
  940.     yLblSize.width  = fontWIDTH(label);
  941.     yLblSize.height = fontHeight;
  942.   }
  943.   if (!cFlags.yLabelRotate) yLS = yLblSize;
  944.   else { yLS.width = yLblSize.height; yLS.height = yLblSize.width; }
  945.  
  946.   /* calculate chart frame */
  947.   chartFrame.X = (cFlags.yShowLabels?yLS.width :0.0) + tickLength / 2.0 + 2.0;
  948.   if (chartFrame.X < xLS.width / 2.0) chartFrame.X = xLS.width / 2.0;
  949.   chartFrame.Y = (cFlags.xShowLabels?xLS.height:0.0) + tickLength / 2.0 + 2.0;
  950.   chartFrame.W = bounds.W - chartFrame.X - (cFlags.xShowLabels?xLS.width *0.50:2.0);
  951.   chartFrame.H = bounds.H - chartFrame.Y - (cFlags.yShowLabels?yLS.height     :2.0);
  952.  
  953.   /* scale factor */
  954.   dataScale.x = dataRange.W? chartFrame.W/(xPOINT(maxRange.x)-xPOINT(dataRange.X)) : 0.0;
  955.   dataScale.y = dataRange.H? chartFrame.H/(yPOINT(maxRange.y)-yPOINT(dataRange.Y)) : 0.0;
  956.  
  957.   /* axis origin */
  958.   axisOrigin.x = chartFrame.X - xSCALE(dataRange.X);  
  959.   axisOrigin.y = chartFrame.Y - ySCALE(dataRange.Y);
  960.   
  961.   /* actual displayed axis position */
  962.   axisPosition.x = xPLOT((maxRange.x > 0.0)? MAX(dataRange.X, 0.0): MIN(maxRange.x, 0.0));
  963.   axisPosition.y = yPLOT((maxRange.y > 0.0)? MAX(dataRange.Y, 0.0): MIN(maxRange.y, 0.0));
  964.  
  965.   return self;
  966. }
  967.  
  968. /* draw Y grid lines (parallel X-axis) */
  969. - _drawYGridLines
  970. {
  971.  
  972.   if (cFlags.yGridDraw && (tickMark.H > 0.0)) {
  973.     float t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
  974.     if (y < dataRange.Y) y += t;
  975.     _setColor(yGridColor, cFlags.drawInColor);
  976.     PSsetlinewidth(tickWidth);
  977.     for (p = y; p <= maxRange.y; p += t) {
  978.       _barRLine(chartFrame.X, yPLOT(p), chartFrame.W, 0.0);
  979.       if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
  980.     }
  981.   }
  982.   
  983.   return self;
  984. }
  985.  
  986. /* draw X grid lines (parallel Y-axis) */
  987. - _drawXGridLines
  988. {
  989.  
  990.   if (cFlags.xGridDraw && (tickMark.W > 0.0)) {
  991.     float t = tickMark.W, x = t * (long)(dataRange.X / t), p;
  992.     if (x < dataRange.X) x += t;
  993.     _setColor(xGridColor, cFlags.drawInColor);
  994.     PSsetlinewidth(tickWidth);
  995.     for (p = x; p <= maxRange.x; p += t) {
  996.       _barRLine(xPLOT(p), chartFrame.Y, 0.0, chartFrame.H);
  997.       if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
  998.     }
  999.   }
  1000.   
  1001.   return self;
  1002. }
  1003.  
  1004. /* draw Y axis */
  1005. - _drawYAxis
  1006. {
  1007.  
  1008.   PSsetlinewidth(tickWidth);
  1009.   _setColor(axisColor, cFlags.drawInColor);
  1010.   _barRLine(axisPosition.x, chartFrame.Y, 0.0, chartFrame.H);
  1011.   
  1012.   return self;
  1013. }
  1014.  
  1015. /* draw X axis */
  1016. - _drawXAxis
  1017. {
  1018.  
  1019.   PSsetlinewidth(tickWidth);
  1020.   _setColor(axisColor, cFlags.drawInColor);
  1021.   _barRLine(chartFrame.X, axisPosition.y, chartFrame.W, 0.0);
  1022.   
  1023.   return self;
  1024. }
  1025.  
  1026. /* draw Y-axis label */
  1027. - _drawYLabel:(int)i:(float)p:(NXPoint*)point
  1028. {
  1029.   char        label[64];
  1030.   NXRect    r;
  1031.   BOOL        stateSaved = NO;
  1032.       
  1033.   /* label size */
  1034.   if (yLabelMatrix) {
  1035.     *label = 0;
  1036.     r.W = yLblSize.width;
  1037.     r.H = yLblSize.height;
  1038.   } else {
  1039.     sprintf(label, "%.*f", precisionY, p);
  1040.     r.W = fontWIDTH(label);
  1041.     r.H = yLblSize.height / 4.0;
  1042.   }
  1043.  
  1044.   /* label orientation */
  1045.   if (cFlags.yLabelRotate) {
  1046.     PSgsave(); stateSaved = YES;
  1047.     PStranslate(point->x - 1.0, MAX(point->y - r.H / 2.0, 0.0));
  1048.     PSrotate(90.0);
  1049.     r.X  = 0.0;
  1050.     r.Y  = 0.0;
  1051.   } else {
  1052.     r.X  = point->x - r.W - 1.0;
  1053.     r.Y  = MAX(point->y - r.H / 2.0, 0.0);
  1054.   }
  1055.  
  1056.   /* draw label */
  1057.   if (yLabelMatrix) {
  1058.     id cellId = [yLabelMatrix cellAt:i:0];
  1059.     if (!cellId) cellId = [yLabelMatrix cellAt:0:i];
  1060.     if (cellId) [cellId drawSelf:&r inView:self];
  1061.   } else _barDrawText(r.X, r.Y, label);
  1062.       
  1063.   /* restore state */
  1064.   if (stateSaved) PSgrestore();
  1065.       
  1066.   return self;
  1067. }
  1068.  
  1069. /* draw Y-axis labels */
  1070. - _drawYAxisLabels
  1071. {
  1072.  
  1073.   if (tickMark.H > 0.0) {
  1074.     NXPoint    point;
  1075.     int        i;
  1076.     float    t = tickMark.H, y = t * (long)(dataRange.Y / t), p;
  1077.     point.x = axisPosition.x - floor(tickLength / 2.0);
  1078.     if (y < dataRange.Y) y += t;
  1079.     if (!yLabelMatrix && cFlags.yShowLabels) [currentFont set];
  1080.     for (i = 0, p = y; p <= maxRange.y; p += t) {
  1081.  
  1082.       /* draw tickMark */
  1083.       point.y = yPLOT(p);
  1084.       _setColor(axisColor, cFlags.drawInColor);
  1085.       PSsetlinewidth(tickWidth);
  1086.       _barRLine(point.x + 1.0, point.y, tickLength, 0.0);
  1087.       if (!cFlags.yShowLabels) {
  1088.         if (cFlags.yLogScale) while (p / t >= 10.0) t *= 10.0;
  1089.         continue;
  1090.       }
  1091.       
  1092.       /* draw label */
  1093.       if (!cFlags.yLogScale) {
  1094.         [self _drawYLabel:i++:p:&point];
  1095.       } else
  1096.       if (p / t >= 10.0) {
  1097.         [self _drawYLabel:i++:p:&point];
  1098.         while (p / t >= 10.0) t *= 10.0;
  1099.       }
  1100.      
  1101.     }
  1102.   }
  1103.   
  1104.   return self;
  1105. }
  1106.  
  1107. /* draw X-axis label */
  1108. - _drawXLabel:(int)i:(float)p:(NXPoint*)point
  1109. {
  1110.   char        label[64];
  1111.   NXRect    r;
  1112.   BOOL        stateSaved = NO;
  1113.       
  1114.   /* label size */
  1115.   if (xLabelMatrix) {
  1116.     *label = 0;
  1117.     r.W = xLblSize.width;
  1118.     r.H = xLblSize.height;
  1119.   } else {
  1120.     sprintf(label, "%.*f", precisionX, p);
  1121.     r.W = fontWIDTH(label);
  1122.     r.H = xLblSize.height;
  1123.   }
  1124.  
  1125.   /* label orientation */
  1126.   if (cFlags.xLabelRotate) {
  1127.     PSgsave(); stateSaved = YES;
  1128.     PStranslate(point->x + r.H / 2.0, MAX(point->y - r.W - 1.0, 0.0));
  1129.     PSrotate(90.0);
  1130.     r.X  = 0.0;
  1131.     r.Y  = 0.0;
  1132.   } else {
  1133.     r.X  = point->x - r.W / 2.0;
  1134.     r.Y  = MAX(point->y - r.H - 1.0, 0.0);
  1135.   }
  1136.  
  1137.   /* draw label */
  1138.   if (xLabelMatrix) {
  1139.     id cellId = [xLabelMatrix cellAt:0:i];
  1140.     if (!cellId) cellId = [xLabelMatrix cellAt:i:0];
  1141.     if (cellId) [cellId drawSelf:&r inView:self];
  1142.   } else _barDrawText(r.X, r.Y, label);
  1143.       
  1144.   /* restore state */
  1145.   if (stateSaved) PSgrestore();
  1146.   
  1147.   return self;
  1148. }
  1149.  
  1150. /* draw X-axis labels */
  1151. - _drawXAxisLabels
  1152. {
  1153.  
  1154.   if (tickMark.W > 0.0) {
  1155.     NXPoint    point;
  1156.     int        i;
  1157.     float    t = tickMark.W, x = t * (long)(dataRange.X / t), p;
  1158.     point.y = axisPosition.y - floor(tickLength / 2.0);
  1159.     if (x < dataRange.X) x += t;
  1160.     if (!xLabelMatrix && cFlags.xShowLabels) [currentFont set];
  1161.     for (i = 0, p = x; p <= maxRange.x; p += t) {
  1162.       
  1163.       /* draw tickMark */
  1164.       point.x = xPLOT(p);
  1165.       _setColor(axisColor, cFlags.drawInColor);
  1166.       PSsetlinewidth(tickWidth);
  1167.       _barRLine(point.x, point.y + 1.0, 0.0, tickLength);
  1168.       if (!cFlags.xShowLabels) {
  1169.         if (cFlags.xLogScale) while (p / t >= 10.0) t *= 10.0;
  1170.         continue;
  1171.       }
  1172.       
  1173.       /* draw label */
  1174.       if (!cFlags.xLogScale) {
  1175.         [self _drawXLabel:i++:p:&point];
  1176.       } else
  1177.       if (p / t >= 10.0) {
  1178.         [self _drawXLabel:i++:p:&point];
  1179.         while (p / t >= 10.0) t *= 10.0;
  1180.       }
  1181.  
  1182.     }
  1183.   }
  1184.   
  1185.   return self;
  1186. }
  1187.  
  1188. /* draw data */
  1189. - _drawChartData
  1190. {
  1191.  
  1192.   PSgsave();
  1193.   _barClipBox(chartFrame.X, chartFrame.Y, chartFrame.W, chartFrame.H);
  1194.   if (dataList) {
  1195.     int s;
  1196.     for (s = 0; s < [dataList count]; s++) {
  1197.       chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:s];
  1198.       if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
  1199.         id list, src = dtaPtr->_srcId;
  1200.         NXStream *s = (dtaPtr->_srcType==srcTypeTEXT)?[src stream]:(NXStream*)src;
  1201.         if (list = [self _parseStream:s:YES]) {
  1202.           int i, cnt = [list count];
  1203.           for (i = 0; i < cnt; i++) {
  1204.             chartData_s *dp = (chartData_s*)[list elementAt:i];
  1205.             dp->lineWidth = dtaPtr->lineWidth;
  1206.             [self _chartData:dp];
  1207.             [dp->_srcId free];
  1208.           }
  1209.           [list free];
  1210.         }
  1211.       } else [self _chartData:dtaPtr];
  1212.     }
  1213.   } else [self _chartData:(chartData_s*)nil];
  1214.   PSgrestore();    // remove clip path
  1215.   
  1216.   return self;
  1217. }
  1218.  
  1219. /* draw all chart types */
  1220. // this method has been arranged to provide for easy subclassing
  1221. - _drawChart
  1222. {
  1223.  
  1224.   [self _drawYGridLines];        // Y axis grid lines
  1225.   [self _drawXGridLines];        // X axis grid lines
  1226.   [self _drawChartData];        // chart data
  1227.   [self _drawYAxis];            // Y axis
  1228.   [self _drawXAxis];            // X axis
  1229.   [self _drawYAxisLabels];        // Y axis labels
  1230.   [self _drawXAxisLabels];        // X axis labels
  1231.   
  1232.   return self;
  1233. }
  1234.  
  1235. /* draw self */
  1236. - drawSelf:(const NXRect *)r :(int)n
  1237. {
  1238.   
  1239.   /* fill background */
  1240.   if (!cFlags.transparent) {
  1241.     _setColor(backgroundColor, cFlags.drawInColor);
  1242.     NXRectFill(&bounds);
  1243.   }
  1244.  
  1245.   /* rescale if necessary */
  1246.   if (reScale) { [self _rescaleAxis]; reScale = NO; }
  1247.   
  1248.   /* draw */
  1249.   [self _drawChart];
  1250.   
  1251.   /* highlight if first responder */
  1252. //if (isFirstResponder) NXHighlightRect(&bounds);
  1253.  
  1254.   return self;
  1255. }
  1256.  
  1257. // -------------------------------------------------------------------------------------
  1258. // object archiving stuff
  1259.  
  1260. - read:(NXTypedStream*)s
  1261. {
  1262.   int    ver;
  1263.   [super read:s];
  1264.   NXReadTypes(s, "i",&ver);
  1265.   NXReadRect (s, &dataRange);
  1266.   NXReadRect (s, &tickMark);
  1267.   NXReadTypes(s, "@is",¤tFont,&dftChartType,&cFlags);
  1268.   if (ver <= 1) { float dmy; NXReadTypes(s, "fffff",&dmy,&dmy,&dmy,&dmy,&dmy); }
  1269.   backgroundColor = NXReadColor(s);
  1270.   dftChartColor      = NXReadColor(s);
  1271.   axisColor          = NXReadColor(s);
  1272.   xGridColor      = NXReadColor(s);
  1273.   yGridColor      = NXReadColor(s);
  1274.   delegate          = self;
  1275.   xLabelMatrix      = (id)nil;
  1276.   yLabelMatrix      = (id)nil;
  1277.   actionMatrix      = (id)nil;
  1278.   dataList        = (id)nil;
  1279.   reScale         = YES;
  1280.   return self;
  1281. }
  1282.  
  1283. - write:(NXTypedStream*)s
  1284. {
  1285.   int    ver = [[self class] version];
  1286.   [super write:s];
  1287.   NXWriteTypes(s, "i",&ver);
  1288.   NXWriteRect (s, &dataRange);
  1289.   NXWriteRect (s, &tickMark);
  1290.   NXWriteTypes(s, "@is",¤tFont,&dftChartType,&cFlags);
  1291.   NXWriteColor(s, backgroundColor);
  1292.   NXWriteColor(s, dftChartColor);
  1293.   NXWriteColor(s, axisColor);
  1294.   NXWriteColor(s, xGridColor);
  1295.   NXWriteColor(s, yGridColor);
  1296.   return self;
  1297. }
  1298.  
  1299. // -------------------------------------------------------------------------------------
  1300. // firstResponder for getting copy/paste commands
  1301.  
  1302. /* allow becoming first responder */
  1303. - (BOOL)acceptsFirstResponder
  1304. {
  1305.   return YES;
  1306. }
  1307.  
  1308. /* become first responder */
  1309. - becomeFirstResponder
  1310. {
  1311.   isFirstResponder = YES;
  1312.   [self display];
  1313.   return self;
  1314. }
  1315.  
  1316. /* resign first responder */
  1317. - resignFirstResponder
  1318. {
  1319.   isFirstResponder = NO;
  1320.   [self display];
  1321.   return self;
  1322. }
  1323.  
  1324. // -------------------------------------------------------------------------------------
  1325. // mouseDown hit detection
  1326.  
  1327. /* handle point selected */
  1328. - _selectedPoint:(chartData_s*)dtaPtr:(NXPoint*)point:(int)index clicks:(int)clicks
  1329. {
  1330.   if (clicks == 2) {
  1331.     if (delegate && [delegate respondsTo:@selector(chartDataSelected:index:)]) {
  1332.       [delegate chartDataSelected:dtaPtr index:index];
  1333.     }
  1334.   }
  1335.   return self;
  1336. }
  1337.  
  1338. /* chart data */
  1339. - (BOOL)_hit:(NXPoint*)pt data:(chartData_s*)dtaPtr hit:(NXPoint*)hit:(int*)index
  1340. {
  1341.   NXPoint    w, h;
  1342.   int        i, cnt = [self _pointCount:dtaPtr];
  1343.  
  1344.   /* valid range */
  1345.   if (dtaPtr->chartType & chartBAR) { w.x = xSCALE(0.8) / 2.0; w.y = 0; }
  1346.   else { w.x = 3.0; w.y = w.x; }
  1347.  
  1348.   /* check hit */
  1349.   for (i = 0; i < cnt; i++) {
  1350.     if (![self _dataItemAt:i :dtaPtr :&h.x:&h.y]) break;
  1351.     if ((pt->x >= h.x - w.x) && (pt->x <= h.x + w.x)                 &&
  1352.         (((dtaPtr->chartType & chartBAR) && (pt->y <= h.y + 8.0)) ||
  1353.          ((pt->y >= h.y - w.y) && (pt->y <= h.y + w.y))             )   ) {
  1354.       *index = i;
  1355.       *hit = h;
  1356.       return YES;
  1357.     } 
  1358.   }
  1359.  
  1360.   /* no hit */
  1361.   return NO;
  1362.  
  1363. }
  1364.  
  1365. /* data point hit detection */
  1366. - _dataHitDetection:(NXPoint*)pt clicks:(int)clicks
  1367. {
  1368.   NXPoint        hPt;
  1369.   int            s, index = -1;
  1370.   
  1371.   /* ignore if no data points */
  1372.   if (!dataList) return self;
  1373.   
  1374.   /*loop through datasets to find hit */
  1375.   for (s = [dataList count]; s;) {
  1376.     BOOL hit = NO;
  1377.     chartData_s *dtaPtr = (chartData_s*)[dataList elementAt:--s];
  1378.     if ((dtaPtr->_srcType == srcTypeTEXT) || (dtaPtr->_srcType == srcTypeSTREAM)) {
  1379.       id list, src = dtaPtr->_srcId;
  1380.       NXStream *s = (dtaPtr->_srcType == srcTypeTEXT)? [src stream] : (NXStream*)src;
  1381.       if (list = [self _parseStream:s:NO]) {
  1382.         int i;
  1383.         hit = [self _hit:pt data:(chartData_s*)[list elementAt:0] hit:&hPt:&index];
  1384.         for (i=0;i<[list count];i++) [((chartData_s*)[list elementAt:i])->_srcId free];
  1385.         [list free];
  1386.       }
  1387.     } else hit = [self _hit:pt data:dtaPtr hit:&hPt:&index];
  1388.     if (hit) { [self _selectedPoint:dtaPtr:&hPt:index clicks:clicks]; break; }
  1389.   }
  1390.  
  1391.   return self;
  1392. }
  1393.  
  1394. /* intercept mouse down */
  1395. - mouseDown:(NXEvent*)e
  1396. {
  1397.   NXPoint    pt = e->location;
  1398.   [self convertPoint:&pt fromView:(id)nil];
  1399.   if (![self mouse:&pt inRect:&chartFrame]) return [super mouseDown:e];
  1400.   if (e->data.mouse.click == 2) [self _dataHitDetection:&pt clicks:2];
  1401.   return [super mouseDown:e];
  1402. }
  1403.  
  1404. // -------------------------------------------------------------------------------------
  1405. // chart data point change/selection delegate methods
  1406.  
  1407. /* data point selected */
  1408. - chartDataSelected:(chartData_s*)dtaPtr index:(int)index
  1409. {
  1410.   id c, m = actionMatrix, srcId = dtaPtr->_srcId;
  1411.   if (!m && [srcId isKindOf:[Matrix class]] && ([srcId target] != self)) m = srcId;
  1412.   if (m && (c = [[m cellList] objectAt:index])) { [m selectCell:c]; [m sendAction]; }
  1413.   return self;
  1414. }
  1415.  
  1416. @end
  1417.